Mastering Programming with MATLAB (Coursera)
Table of Contents
Object-Oriented Programming
Introduction
What we have learned so far falls within the category o fprocedural programming. We have organized ourcode into functions,typically creating a main function that utilizes helper functions to compute whatever we needand then returns the results to the Command Window.
Procedural programming focuses on the operationsa program needs to do. But as the size and complexity of the program grow, it becomes more and more difficult to manage the data that the program works on. Having only global and local variables and function arguments at our disposal makes it harder to keep track of the data, to make sure that we do not make unintended changes to it, and to understand and modify existing programs. Object-Oriented Programming (OOP) helps with all of these problems.
Object-Oriented Programming, unlike procedural programming,
- is centeredon data as opposed to functions.
- The object in Object-Oriented Programming consists of one or more data fields similarly to a struct. But unlike structs, an object also contains functions that operate on the data.
- A cool feature of an object is that you can control which data fields (and which functions) are accessible outside of the object. Let’s say, for example, that you need a sorted-vector data type. Using procedural programming, you would create a regular vector v and write functions to insert a new element into it, to remove an element from it, and to find and element within it. However, the vector itself will be accessible from other functions as well, and it can be inadvertently modified.
Object- Oriented Programming
- OOP is data-centered
- Data stored inside an "object."
- Object is like a stuct with wxtra features
- Object can contain functions.
- Object is stored in a variable
- object's type must be defined by users
- User0-defined type="class"
- Classes are defined using keyword classdef
struct function
ParamodkumarYadav %error insert
veryCoolguy=struct('name',[])
veryCoolguy.name='Pramod Yadav'
veryCoolguy.game='Learning'
help struct
Class Definition
A class definition starts out with that keyword classdef. And it's followed by the name of the class that we want to define (Contact in example), which in this case is contact. And we follow the object oriented convention of starting the name of the class that we're defining with an uppercase letter.
The reason that I quickly saved this file with the name contact, is that a class definition is legal only if it's defined in an m file, and only if that m file has the same name as the class.
Inside our contact class,
we've added three properties after the key word properties.
Each property is a data field just like the fields in a struct.
And here again, we've started the property names with uppercase letters, and
we've used uppercase letters inside the names to highlight where new words begin.
% classdef Contact
% %Version1
% properties
% FirstName
% LastName
% PhoneNumber
% end
% end
see how to create an object to this class.
person=Contact
- this command creates an object of the class contact and assigns that object to the variable person. And during the creation of that object, MATLAB assigned a value to each one of the objects properties. And the value it chose was the empty array.
- So we need to get busy assigning them the values that we want them to have. Now we can do that in just the same way that we assign values to fields of the struct, very cool guy.
person.FirstName="Pramod"
person.LastName="Yadav"
person.PhoneNumber="9454354355"
We can get this work done with just one command that creates an object and gives values to its properties, if we introduce a couple more features.
that is that we can add functions to an object. We do it by putting function definitions inside the definition of the objects class.This function definitions appeared a new section of the class which is introduced by the key word methods.The word methods is used because in MATLAB a function defined inside a class is called a method.
The name is not unique to MATLAB. In fact, it's older than MATLAB itself.
That's our first example of a method. We're going to introduce another feature of object oriented programming. The constructor.
A constructor is exactly what we need to avoid the cumbersome approach of adding values one by one to the properties of an object after we create it. A constructor is a method that creates an object and gives values to its properties at the same time. Let's add a constructor to our contact class
% classdef Contact
% %Version2
% properties
% FirstName
% LastName
% PhoneNumber
% end
% methods
% function obj = Contact(lname,fname,phone)
% obj.LastName = string(lname);
% obj.FirstName = string(fname);
% obj.PhoneNumber = string(phone);
% end
% end
% end
Usually a method does not have the same name as its class, but this function is a constructor. And a constructor must have the same name as the class it's defined in.The output of a constructor is the new object that it creates. And in the body of that constructor, it builds that object and assigns it to its formal output argument, which we've named OBJ.This constructor has three formal input arguments, lname, fname, and phone.
There is no restriction on the number of formal arguments, and it can in fact,
But the possibility of having varying numbers of actual arguments is especially important for constructors. Because unlike most object oriented programming languages, in MATLAB, a class can have only a single constructor.
lets create new object-
person2=Contact("lodhi","ketan","882146130")
we make sure that each property of the contact class is a string.Note how we use a double for the phone number when we call the constructor. But the property itself becomes a string because we make sure of that and the constructor method.
Still at this point, this is not much different from a struct.So what's the big deal about classes and object oriented programming?
Well, let's consider a problem with our current class.If I wanted to modify our property, I could do it without restrictions and I could mess up the object.
person2.LastName=pi
A number for a last name doesn't make sense. And even worse, it can break other parts of our program later since we assume it was a string.
So it's much better to design classes for which users of the class cannot violate the assumptions.That's actually one of the reasons object oriented programming was invented.
So in our simple case, how can we prevent somebody from accidentally changing the last name to a number?
Well, we add a special kind of method to the object called an Access method.
Lets Write our 1st access methode
% classdef Contact
% properties
% FirstName
% LastName
% PhoneNumber
% end
% methods
% function obj=Contact(lname,fname,phone)
% obj.LastName=string(lname);
% obj.FirstName=string(fname);
% obj.PhoneNumber=string(phone);
% end
% function obj=set.LastName(obj,lname)
% obj.LastName=string(lname);
% end
% end
% end
We set the last name property to the new value, making sure that it's a string.
Let's try it.
% person2.set.LastName("Lodhi")
You're not allowed to use an access method directly. The whole point is that when someone tries to assign a new value to the property by using the dot operator, MATLAB will check to see whether there is a set access method for this property. And this access function will get called automatically.
person2.LastName="lodhi"
Well, we changed the last name.
lets agin assign pi
person2.LastName=pi
The last name property is not pi, it's the string 3.1416 which is what the string function produces when you give it the argument, pi.
we could go further with our access method and have it checked to determine whether someone tries to assign a number to the last name property and send an error back if they do. But for our simple case, we'll just leave it as it is.
let's add access methods to the rest of the properties.
% classdef Contact
% %Version3
% properties
% FirstName
% LastName
% PhoneNumber
% end
% methods
% function obj = Contact(lname,fname,phone)
% obj.LastName = string(lname);
% obj.FirstName = string(fname);
% obj.PhoneNumber = string(phone);
% end
% function obj = set.LastName(obj,lname)
% obj.LastName = string(lname);
% end
% function obj = set.FirstName(obj,fname)
% obj.FirstName = string(fname);
% end
% function obj = set.PhoneNumber(obj,phone)
% obj.PhoneNumber = string(phone);
% end
% end
% end
So here's the latest version of the class.
provide get access methods to our properties. Which are also known as property get methods. Now, for our simple contact class, this is not really necessary. Here's the syntax to show how we would do it.
% classdef Contact
% properties
% FirstName
% LastName
% PhoneNumber
% end
% methods
% function obj = Contact(lname,fname,phone)
% obj.LastName = string(lname);
% obj.FirstName = string(fname);
% obj.PhoneNumber = string(phone);
% end
% function obj = set.LastName(obj,lname)
% obj.LastName = string(lname);
% end
% function obj = set.FirstName(obj,fname)
% obj.FirstName = string(fname);
% end
% function obj = set.PhoneNumber(obj,phone)
% obj.PhoneNumber = string(phone);
% end
% function lname=get.LastName(obj)
% lname=obj.LastName;
% end
% end
% end
The method name is get dot property name. The single input argument is the object. And the single output argument contains the value of the property that we're returning.Get access methods are called whenever we query the value of a property.
For example,
person=Contact("Yadav","Pramod","9454354355")
But how do we check to see whether this get access method is actually being called?
Well, we can use a break point again. Or we can temporarily modify the method say like this.
% classdef Contact
% properties
% FirstName
% LastName
% PhoneNumber
% end
% methods
% function obj = Contact(lname,fname,phone)
% obj.LastName = string(lname);
% obj.FirstName = string(fname);
% obj.PhoneNumber = string(phone);
% end
% function obj = set.LastName(obj,lname)
% obj.LastName = string(lname);
% end
% function obj = set.FirstName(obj,fname)
% obj.FirstName = string(fname);
% end
% function obj = set.PhoneNumber(obj,phone)
% obj.PhoneNumber = string(phone);
% end
% function lname=get.LastName(obj)
% lname="yaduvanshi"; %change
% end
% end
% end
Now run this
person=Contact("Yadav","Pramod","9454354355")
hence get access method is being called.
now lets change back to
% classdef Contact
% properties
% FirstName
% LastName
% PhoneNumber
% end
% methods
% function obj = Contact(lname,fname,phone)
% obj.LastName = string(lname);
% obj.FirstName = string(fname);
% obj.PhoneNumber = string(phone);
% end
% function obj = set.LastName(obj,lname)
% obj.LastName = string(lname);
% end
% function obj = set.FirstName(obj,fname)
% obj.FirstName = string(fname);
% end
% function obj = set.PhoneNumber(obj,phone)
% obj.PhoneNumber = string(phone);
% end
% function lname=get.LastName(obj)
% lname=obj.LastName;
% end
% end
% end
These contact objects each have properties for storing the first and last names of persons as well as their phone numbers.Well, what if we wanted to store not just personal contact information, but business information too.
Our existing contact class wouldn't work for that because every business contact needs a property to hold the company name. And the contact class doesn't have one.We could add a company name property, of course, but it wouldn't get used for personal contacts, so we'd have a wasted slot sitting there empty for all of them. And it's not ideal for another reason too.We might want slightly different behavior for personal contacts from business contacts.
Well, another option would be to create a brand new business contact class by copying the contact class, adding a property to the copy and renaming it.But there's a disadvantage to that too. Because if we make some improvements to the way that contact class handles its names and the phone number and to get those changes into the business contact class. We'd have to copy him into it.
Inheritance
one of the most important features of object oriented programming is that one class can build on another class. And that feature even has a name, it's called inheritance. We can create a new class and specify that it is, indeed, a new modified or derived version of another class. Such a class is called a subclass, and it inherits all the properties and methods of its super class.Which, somewhat confusingly, is also called a base class.
It can modify methods that it inherits, and it can add its own properties and methods, as well.
Let's create a new BusinessContact class from the existing Contact class.
% classdef BusinessContact < Contact
% %version1
% properties
% Company
% Fax
% end
% end
that's the constructor of the Contact class. It's expecting inputs, and we didn't give any. Well, wait a minute, before we solve that problem, how did we get to the constructor of the Contact class when we were constructing a BusinessContact object?
Well, a BusinessContact is a subclass of the super class Contact. And whenever MATLAB creates an instance of a subclass, it needs to create an instance of the super class first.
subclass gets all the properties of the super class, and the way that happens is that the super class object becomes kind of a part of the subclass object. And when you try to create a subclass instance, MATLAB first calls the super class constructor, which in this case is Contact. And that Contact constructor has three input arguments. So we had better supply them or get ready to face a sea of red.
b=BusinessContact("Gates","Bill",555555555)
But just like with a Contact class, it would be better if we had a constructor that also initialized the company name and the fax number at once.
Let's do that.
% classdef BusinessContact < Contact
% %version2
% properties
% Company
% Fax
% end
% methods
% function obj = BusinessContact(cname,lname,fname,phone,f)
% obj.LastName = string(lname);
% obj.FirstName = string(fname);
% obj.PhoneNumber = string(phone);
% obj.Company = string(cname);
% obj.Fax = string(f);
% end
% end
% end
%b=BusinessContact("MS","Gates","Bill",5555555555,5874857)
What's the problem now?
Well, we just discussed the super class constructor will be called by MATLAB. But it expects three input arguments. In this case we've not told MATLAB what arguments it should use, so it uses none.
But our Contact class constructor is not prepared to be call with zero arguments. There are two solutions. First, we should prepare the Contact constructor to be called with zero input arguments.
let's make it able to accept any number of arguments from zero to three.
% classdef Contact
% %Version4
% properties
% FirstName
% LastName
% PhoneNumber
% end
% methods
% function obj = Contact(lname,fname,phone)
% if nargin < 3, phone = ""; end
% if nargin < 2, fname = ""; end
% if nargin < 1, lname = ""; end
% obj.LastName = string(lname);
% obj.FirstName = string(fname);
% obj.PhoneNumber = string(phone);
% end
% function obj = set.LastName(obj,lname)
% obj.LastName = string(lname);
% end
% function obj = set.FirstName(obj,fname)
% obj.FirstName = string(fname);
% end
% function obj = set.PhoneNumber(obj,phone)
% obj.PhoneNumber = string(phone);
% end
% end
% end
Contact("Lodhi")
Contact("Lodhi","Ketan")
Contact("Lodhi","Ketan","9543558258")
person2=Contact("lodhi","ketan","882146130")
let's see whether we fixed our BusinessContact constructor issue.
b=BusinessContact("MS","Gates","Bill",5555555555,5874857)
Well, that did it.
The first thing MATLAB did was call the Contact constructor with zero arguments. Then we set all the properties of the new business class, its own, as well as the ones it inherited from Contact, one by one.
I mentioned that there were two solutions to the problem we were having with creating a new BusinessContact instance.
What's the second solution?
Well, instead of relying on MATLAB to call the super class constructor, why don't we call it ourselves? In that case, we can call it the way we want to.
For example, instead of manually setting the last name and the other inherited properties, we can simply call the super class constructor that already does that anyway. Here we got.
% classdef BusinessContact < Contact
% %version3
% properties
% Company
% Fax
% end
% methods
% function obj = BusinessContact(cname,lname,fname,phone,f)
% obj@Contact(lname,fname,phone);
% obj.Company = string(cname);
% obj.Fax = string(f);
% end
% end
% end
You have to use the @ sign operator with the output object and the super class name to call the super class constructor.
Then you can supply whatever arguments you want.
Most important rule, as far as calling the super class constructor is concerned, is that it must be called before the object variable is used for anything else. Two final improvements to the business class await.
Just as we did for the Contact constructor, we should prepare the BusinessContact constructor to be able to accept any number of arguments up to five.
Let's do that.
% classdef BusinessContact < Contact
% %version4
% properties
% Company
% Fax
% end
% methods
% function obj = BusinessContact(cname,lname,fname,phone,f)
% if nargin < 5 f = ""; end
% if nargin < 4 phone = ""; end
% if nargin < 3 fname = ""; end
% if nargin < 2 lname = ""; end
% if nargin < 1 cname = ""; end
% obj@Contact(lname,fname,phone);
% obj.Company = string(cname);
% obj.Fax = string(f);
% end
% end
% end
And our final tweak to the BusinessContact class is to add set access methods for the new properties.
% classdef BusinessContact < Contact
% %version5
% properties
% Company
% Fax
% end
% methods
% function obj = BusinessContact(cname,lname,fname,phone,f)
% if nargin < 5 f = ""; end
% if nargin < 4 phone = ""; end
% if nargin < 3 fname = ""; end
% if nargin < 2 lname = ""; end
% if nargin < 1 cname = ""; end
% obj@Contact(lname,fname,phone);
% obj.Company = string(cname);
% obj.Fax = string(f);
% end
% function obj = set.Company(obj,cname)
% obj.Company = string(cname);
% end
% function obj = set.Fax(obj,f)
% obj.Fax = string(f);
% end
% end
% end
Well, that's the final tweak to the BusinessContact class,
I've thought of a tweak that I should've made to the Contact class. And I should have thought of it before I defined the BusinessContact class, dad-blast-it!
What I should've done is add a method for printing the name of the Contact in the command window.And the BusinessContact class could have inherited the dad-blasted thing.
Well, better late than never, I'll go ahead and add it now
% classdef Contact
% properties
% FirstName
% LastName
% PhoneNumber
% end
% methods
% function obj = Contact(lname,fname,phone)
% if nargin < 3, phone = ""; end
% if nargin < 2, fname = ""; end
% if nargin < 1, lname = ""; end
% obj.LastName = string(lname);
% obj.FirstName = string(fname);
% obj.PhoneNumber = string(phone);
% end
% function obj = set.LastName(obj,lname)
% obj.LastName = string(lname);
% end
% function obj = set.FirstName(obj,fname)
% obj.FirstName = string(fname);
% end
% function obj = set.PhoneNumber(obj,phone)
% obj.PhoneNumber = string(phone);
% end
% function printName(obj)
% fprintf('%s %s\n',obj.FirstName,obj.LastName)
% end
% end
% end
.There it is and, by the way, this is what most ordinary methods look like. By ordinary, I mean that it's not a constructor, which creates a new object and has to have the same name as the class of the object. And it's not an access method, which accesses a property of the object that it's acting on, and whose name has to have a dot in it.
person.printName
Even though I created the object in the variable name person way before I added the print name method to the contact class, it's working just fine.This tells you that you can modify a class anytime you want to, and the modifications will ripple out to all its objects old or new.
person2.printName
b.printName
So inheritance works no matter whether we declare a subclass before or after the change to the superclass.
Do you see the significance of this ripple effect?
The object stored in the variable b is an instance of the business contact class, which we did not change. But since it's a subclass of the superclass Contact, which we did change, the object in b inherits the new method as well as everything else it got when we created it.It's like a child inheriting an estate from a parent.
Let's just say that when you change a class, that change flows out to every object of the class and every object of every subclass for which it served as a superclass. This is a major advantage of inheritance because it maintains consistency without the need to pour through thousands of lines of code looking for places where changes need to be made by hand. And it's another great thing about object-oriented programming.
Summary
Finally, we learned how to create three special kinds of methods, the constructor, which assists in the creation of new objects. The set access method which assists in setting values of properties of objects. And the get access method which assists in the retrieving of the values of the properties of objects.
Practice Quiz
1.Question An object’s type ...
- is defined by MATLAB.
- is called “method.”
- is called “classtype.”
- is defined using keyword “classdef."*
2.Question In OOP, the following statements are true about an object except for
- Objects can contain data and functions
- Objects are stored in a variables.
- An object is like a struct data type with fewer features.*
- All of the above are correct.
3.Question A class can contain the following except for
- s�ave-access method*
- constructor method
- properties
- set-access method
4.Question A new sub-class that inherits from a super class (or a base class) cannot
- call the super class constructor after the object has been used in the class.*
- create new properties and methods.
- modify the set-access methods inherited from the super class.
- inherit all the properties from the super class.
5.Question If a user made a change in a class definition,
- the change affects the current instances of that class.*
- the change does not affect the current instances of its sub-classes.
- �the current instances of the class are only affected if the change is in a method.
- nothing will change in the current instances.
Handle Classes
We're going to create a data structure that's widely used in computer science, but it's not built into MATLAB directly.
Why do we need yet another data structure? Well, MATLAB is built around matrices and they're extremely useful of course, but they do have a disadvantage in that their size is fixed. For example, when we create a new matrix, MATLAB finds a place in the memory where it'll fit and allocates space there. Of course, MATLAB allows us to increase the size of the matrix later on, which requires allocating new space for the additional elements. There's no way around that, but the nature of a matrix and its indexing requires that the entire matrix must fit into one contiguous set of memory cells. It's very often necessary for MATLAB to allocate new space not just for the additional elements, but for new copies of all the existing elements in the matrix as well. It has to do that because there's usually not enough empty room next to the existing elements to accommodate the new ones. MATLAB accomplishes this expansion by stopping your program in the middle of a command, allocating the new space, copying all the existing values into it, and then continuing execution from where it left off. It does all this behind the scenes so we don't have to think about it. But when we're dealing with large matrices and frequent size changes it can slow our program way down. It's for this very reason that in our introductory course we recommended that you preallocate the full size of every matrix. That's always a good idea of course, but sometimes it's just not possible to know in advance what that full size is going to be and that brings us to the answer to why we need another data structure.
Pne such data structure is linked list.
Linked List
A linked list consists of set of individual elements linked into a chain. Eachelement stores some kind of data, for example, a contact object in a datafield, and it has in addition a field typically called next that points to the nextelement in the list. The next field of the last element of the list is set to 0 inmost languages. In MATLAB it is an empty array. The next field makes iteasy to traverse a list. You start at the beginning (the head of the list usingcomputer science terminology) and follow the next field to move from elementto element until you encounter the element with a null next field. This is the last element of the list called the tail.
A variant of the linked list is the doubly linked list whose elements have anadditional field typically called prev that points to the previous element of the list. Here is a visual depiction of a doubly linked list of three elements:
The labels next, DATA, and pref depict fields of individual elements on the list. Each box with these three labels depicts one element of the list. The element at the left is the head of the list. The element at the right is the tail. The boxes labeled head and tail are not the head and tail of the list. In fact they 318 are not part of the list at all but instead are merely pointers to the head and tail of the list.
If we need to insert a new item into this list then instead of creating a new structure and copying the existing data into it, as we would need to do with an array, with the linked list structure we need only change a couple of next and prev pointers to make that happen. Let's say that we want to insert a new node in this linked list between the first and the second nodes.
Let's look at the steps we'd need to follow after we've created the new node that we want to insert and let's say that the data in the new node that we've created is the letter B. First we'll change B's prev and next pointers to point to A and C respectively as shown. Next we'd change A's next pointer to point to node B. Finally, we change C's prev pointer to point to B and that's it.
To make the figure look better we can rearrange it like this.
One leftover thing is this node variable pointing to B. We can leave it alone or we can clear the variable from the workspace, depending on whether we need it later or not.
Now, if we needed to insert a new node at the beginning or at the end of the list, the algorithm would be slightly different, we would need to change the head or the tail pointer. You can also imagine how to remove an element by updating the next pointer of the element before it and the prev pointer of the element after it.
MATLAB won't allow more than one cell to point at the same address in memory. So if you decide to implement a doubly-linked list with cells, it's game over before the game begins. we're going to demonstrate it right now, this time with a card trick.
First, let's pick a card.
cell_a={'jack os Spades'}
cell_b= cell_a
cell_a{1}
cell_b{1}
isequal(cell_a,cell_b)
The braces around the ones tell MATLAB that we want to see the actual card that the cells are pointing at, and we even used is equal to make sure that the addresses in these cells are equal.
Again, the braces around the one tell MATLAB that we want to work with the thing that cell B is pointing at, and we change just the first four characters from J-A-C-K to K-I-N-G
cell_b={'King of Spades'}
cell_a{1}
Even though it seemed like both cell A and cell B were pointing at the same thing, they were not. When we set cell B equal to cell A, instead of directly copying the pointer in cell A into cell B, MATLAB reach behind its back and quickly made a copy of the string jack of spades and then slip the pointer to the copy inside cell B. At that point, cell A was pointing at one jack of spades, and cell B was pointing at another one, and the here is equal thing?
When is equal returns true for two cells, it only means that they're equivalent, which means that they point at data objects that are equal. It doesn't mean that the addresses in the cells are equal.
Hence, no two cells can point at the same object.
Handle class
he handle class gives us exactly what we need to implement a doubly-linked list.
MATLAB uses the term handle for other things too, like figure handles, and axis handles, and such as that.
All of these uses of the word 'Handle' have the idea in common of one thing providing some sort to another thing.
For example, a function handle gives you access to a function that allows you to call it. A file handle gives you access to the contents of a file. A handle class is a new example of something that grants access to something else with the important difference that you get access to an object.
But the first thing to know about them is that when you assign a handle object to a variable, the variable gets a pointer to the object instead of the object itself.
Just the way a cell variable gets a pointer. But with the handle object, you don't have to use those special braces to get the pointer. Edges happens automatically. The pointer that it automatically gets is called a handle, and a handle includes the address of the object along with some other information. The variable with the handle in it, which we'll call a handle variable, is similar to a cell variable, like cell A or cell B. But, and this is a big but,
MATLAB allows to handle variables to point at the same handle object, which is why I said that it's exactly what we need to implement a doubly-linked list.
Let's see a comparison of a handle class and a non handle class, which is called a value class.
Here I've set up definitions of a value class named Mike on and a handle class named Akos.
Value Class (Mike)
% classdef Mike
% properties
% card = 'Jack of Spades'
% end
% end
Handle class (Akos)
% classdef Akos < handle
% properties
% card = 'Jack of Spades'
% end
% end
You can see right here how we make a handle class. We use the less than sign and the word handle, which is the same syntax we use to turn our business contact into a subclass of the superclass named contact. This word handle is not a keyword. Well, I guess you can see that since it's not blue, handle is the name of a built-in class, and to turn a new class into a handle class, you simply make it a subclass of the built-in class named handle.
Let's step through TryMike first. This first step assigns a Mike object to CoolGuy. This next step copies the object in CoolGuy to another variable called CoolCopy. Now we'll change the property, just like we did with the sell variable. We'll check the property using CoolCopy.
CoolGuy=Mike
CoolCopy=CoolGuy
OtherGuy.card(1:4) = 'King'
CoolCopy.card
Well, this is exactly what happened with the cell variables. When we assigned CoolGuy to CoolCopy, MATLAB reached behind its back and made a copy of the object in CoolGuy and slipped it into CoolCopy. So when we changed CoolGuy's property to King of Spades, CoolCopy was still stuck with a Jack.
Now, let's repeat these same operations with a handle class.
OtherGuy=Akos
OtherCopy=OtherGuy
OtherCopy.card(1:4)='king'
OtherCopy.card
But this time, both objects get the king because OtherGuy and OtherCopy are handle variables, and they both automatically got pointers to the same object. Finally, we're getting somewhere. This distinction between normal MATLAB variables and handle variables has other important consequences, too.
For example, do you remember when we talked about the call stack in our first lecture on recursion? Yeah, that's what I thought. Let's review that, but this time, we'll do it with objects. Here on the left is a function name set_value in the editor window with the command window below it. On the right side, the blue box represents the memory, with a call stack on the left, and the base frame for the command window's workspace at the bottom. The rest of the memory is on the right.
We first assign an object of value class to x and set a property named Value equal to 2. We aren't showing the class definition because, frankly, we don't care what it looks like, except that it has a property named Value.
The phrase by value means that the value in the actual argument x is copied from the caller into the formal argument y of the function set_value, and pushed on to the stack. So what does the operation look like when the thing being copied is an instance of a value class? Well, it looks like this. As we learned with our Jack and King card trick, MATLAB makes a copy of the object and places it inside y. Next, after the actual argument is copied into the formal argument, the body of the function is executed, and the formal arguments value property is changed to three. But when the function returns, that formal argument is treated like any other local variable. It's popped off the stack, and it's thrown away.
The actual argument x was never even touched by set_value. Its value property has exactly the same value it had before the function was called, two. That's the end of our review of the call stack.
Here we have the same function set_value in the editor window again, but in the command window, instead of assigning a value object to x, we assign it a handle object. Then, once again, we set its property value equal to two. The difference is that, as we learned with the card trick, the handle variable x gets only a handle with a pointer to the object. The object itself is allocated some space somewhere in the rest of the memory as we show
The function being called gets only a copy of the handle in its formal argument Y. However, and this is a big however, that copy of the handle in the formal argument refers to exactly the same object that the actual argument does. Because they're both pointing at the same object, it's possible for the function to use the handle in its formal argument Y, to reach out and touch an object outside itself. In particular, it can change the values of its properties, and that's exactly what it does. When the function returns, the formal argument is, as in the previous example, popped off the stack and thrown away.
As with the example, with the value object, the actual argument, like all actual arguments in all function calls, was never touched by the function. It was the object that it was pointing at that was changed. So and this is a big so, after the function returns, the property value of the object pointed at by the handle variable X, has a different value from what it had before the function was called. That ability of the function to change an object referenced by a handle variable is the consequence we were talking about.
here, is that the accessibility of the objects is determined not by where the function is defined, but by what type of input the caller passes to it. If it passes in a value object, the function cannot change it. If it's a handle object, it can.
Consider this class definition.
% classdef TestClass %version1
% properties
% Value
% end
% methods
% function obj = TestClass(val)
% if nargin < 1
% obj.Value = 0;
% else
% obj.Value = val;
% end
% end
% function set_value(obj,val)
% obj.Value = val;
% end
% end
% end
We didn't put a less than sign here, so this is not a subclass of any other class and in particular, it's not a handle class. So it's a value class.
Note that this is not the official set access method that we introduced in our previous lecture. That would have the name set.value instead of set_value. Now, this is just a method that happens to set the value property, or does it? Let's try.
brandon=TestClass(2)
brandon.set_value(3)
brandon.Value
Clearly it doesn't.
We shouldn't be surprised,because this is exactly the same thing that we showed you in our schematic depiction of what goes on when we passed a value object to set value. It looks a little different because this time the function that we're calling is a method and we called it with the dot operator.
But here let me show you a different way to call the method set value.
set_value(brandon,3)
brandon.Value
it accomplishes exactly the same thing as the dot operator version of the call, which is, well, nothing.
We hit the break point. Let's take one step, then check the value property.
It's clear that set value does set the value property to 3. But as we were reminded in our review of the call stack, the only object whose property was changed is the one in the formal argument here inside the set value function itself, obj, not the one in the actual argument over here in the command window. Let's continue our execution and look at the actual argument after this function returns. Still equals 2.
So is it even possible to change the value of a property of an instance of a value class from within
its own methods? Well, no and yes.
No, the function can't do it, but yes, you can do it. you can do it in the same way that you make a change using an ordinary function with ordinary variables. Let's say for example,
x=5
x=sqrt(x)
that you've set the variable x equal to some number, and you want to use the square root function to change x to the square root of x. You might do it like this.
So we passed x as an input argument to the square root function, and then assigned the output from the function back to x. That's exactly what we need to do with our set value method in order to change the object.
% classdef TestClass %version2
% properties
% Value
% end
% methods
% function obj = TestClass(val)
% if nargin < 1
% obj.Value = 0;
% else
% obj.Value = val;
% end
% end
% function obj = set_value(obj,val) %change
% obj.Value = val;
% end
% end
% end
Now we can use set value to change the object in exactly the same way that we use the square root function to change x.
brandon=set_value(brandon,3)
brandon=TestClass(2)
brandon=brandon.set_value(3)
same result.
Of course we don't assign the value returned by the method to the variable, no change takes place.
brandon.set_value(4)
brandon.Value
While the object that was returned in this first command does have a property value of four, brandon is still stuck with three. Since this behavior is somewhat counter-intuitive, we strongly suggest that when you're dealing with value classes, you use the ordinary function calling syntax like this,
brandon=set_value(brandon,4)
instead of the object.methodname approach. Doing that will help you remember that you need to assign the return value to the object if you want properties that are changed inside the method, to be changed outside the method. So the bottom line of this example is that if you want to use the value class method to change a property, it must return the object as an output argument, that's the only way to make the change stick.
Now, what if test class was a handle class instead of a value class? Let's try that. All we have to do is make it a subclass of the built-in handle class.
Presto, test class is now a handle class. Let's remove the output argument from the set value method, and let's create an object of this new handle class and call the set value method.
% classdef TestClass < handle
% properties
% Value
% end
% methods
% function obj = TestClass(val)
% if nargin < 1
% obj.Value = 0;
% else
% obj.Value = val;
% end
% end
% function set_value(obj,val)
% obj.Value = val;
% end
% end
% end
call the set value method.
brandon=TestClass(2)
brandon.set_value(3)
brandon
It worked.The same method that failed to do anything when TestClass was a value class, changed the value parameter to three after we made TestClass a handle class. Of course, this should also not be surprising because as we saw in our schematic example for the handle variable, the actual argument Brandon in the command Window and the formal argument OBJ in set value, both contain handles for the same object.
to use the dot operator to call a method only for handle classes and stick with the old all arguments in parentheses way for value classes.
Now that we know all this stuff about passing objects to and from methods, let's get back to linked lists. There are many ways to implement a doubly linked list in MATLAB or in any other object-oriented programming language.
The list itself has multiple nodes plus a head and a tail. Let's create two classes. A doubly-linked list class, which we'll call DList, and a node class, which we'll call LetterNode.
But before we create either of those classes, we're going to create a superclass called LinkedNode.
% classdef LinkedNode < handle %version1
% properties
% Prev
% Next
% Owner
% end
% methods
% function node = LinkedNode()
% node.Prev = [];
% node.Next = [];
% node.Owner = [];
% end
% end
% end
As you can see, it's a handle class and it has three properties; prev, next, and owner. The first two have obvious uses. They provide access to the previous node in the list and the next node in the list. But what about this owner property here? Well, it's going to provide access to the linked list that contains the node.
Here's another handle class. The first part is pretty easy. It's got three properties, head, tail, and length. Its constructor sets head and tail to the empty array and sets the length equal to zero. But now comes the interesting part, the method that inserts a new item into the list. In this first version, the insert method always puts the new element at the end of the list. The first statement of the body of the function insert might need a little explanation. As we said earlier, a node can't be linked into two lists at once and to enforce that rule, we start by checking to see whether it has an owner. If it does, then we check to see whether it's owner is the list we're adding it to. If so, we don't need to do anything to insert it, so we return. If it's owned by some other list, we remove it from that list.
That's done by this statement, node.Owner.remove(node).
This is the first time we've seen the dot operator show up twice in one statement in object-oriented programming. As we learned in our introductory course, MATLAB always uses left-to-right associativity, which means simply that the left dot operator operates before the right one does. Node.owner is evaluated first and the result is some other DList. Now the second dot operation is carried out with this resulting DList on the left and the method named remove on the right. As we've explained before, when MATLAB sees a function like this remove, it checks its input arguments to see if any of them are objects. On the upper hand on the left of the dot operator is one of the input arguments of the method. Since it's an object, MATLAB executes it's remove method.
% classdef DList < handle
% properties
% Head
% Tail
% Length
% end
% methods
%
% function list = DList()
% list.Head = [];
% list.Tail = [];
% list.Length = 0;
% end
%
% function insert(list,node)
% if ~isempty(node.Owner)
% if node.Owner ~= list % New node is in another list,
% node.Owner.remove(node); % so we need to remove it.
% else
% return; % New node is already in this list,
% end % so do nothing.
% end
% if list.Length == 0 % If the list is empty,
% list.Head = node; % put new node at the head,
% else
% list.Tail.Next = node; % else, point tail node at it.
% end
% node.Next = []; % New node is at the end.
% node.Prev = list.Tail; % Previous node is old tail node.
% list.Tail = node; % Make Tail node point at new node.
% list.Length = list.Length + 1;
% node.Owner = list;
% end % insert
%
% function remove(list,node)
% if isempty(node) || node.Owner ~= list
% error('node is not in the list');
% end
% if ~isempty(node.Prev) % If a node precedes the current node,
% node.Prev.Next = node.Next; % make preceding node point to the node
% else % that follows the current node,
% list.Head = node.Next; % else make Head point to it.
% end
% if ~isempty(node.Next) % If a node follows the current node,
% node.Next.Prev = node.Prev; % make its prev point to the node that
% else % that precedes the current node
% list.Tail = node.Prev; % else make previous node be the tail.
% end
% list.Length = list.Length - 1;
% node.Next = [];
% node.Prev = [];
% node.Owner = [];
% end % remove
%
% function displayList(list)
% item = list.Head;
% while ~isempty(item)
% item.disp
% item = item.Next;
% end
% end
% end
% end
The next logical step would be to test these methods. We can't do that just yet. First, we need to create a subclass of linked node to store some data. Remember linked node is not supposed to be used directly to create instances. We're going to define sub-classes of linked node with places for data and create instances of those. For simplicity, let's use a trivial class that stores just letters. This class looks too simple to be worth anything. But don't forget that it inherits all the properties and all the methods of its super-class linked node. They're added to the one property letter and one constructor method that we encoded explicitly in its definition.
% classdef LetterNode < LinkedNode
% properties
% Letter
% end
% methods
% function obj = LetterNode(l)
% if nargin < 1
% obj.Letter = ' ';
% else
% obj.Letter = l;
% end
% end
% end
% end
Now we're ready to create a linked list, that is a DList with multiple letter nodes.
Now we're ready to create a linked list, that is a DList with multiple letter nodes. I prepared a live script that we can step through.
clear
mylist = DList
Set mylist equal to DList and of course, there's nothing on it.
a = LetterNode('A')
b = LetterNode('B')
c = LetterNode('C')
We'll put a letter node containing the character A and the variable a and a B and b, and a C and c.
Now let's insert the nodes into the list one by one. We insert a. We insert b. We insert c.
mylist.insert(a)
mylist.insert(b)
mylist.insert(c)
mylist
It says it has three elements, so far so good.
It should be an ABC order since insert heads its element at the end of the list.Let's see if they are.
Look at the Head, that's A.
Now, let's look at the next node after the Head, that's B.
The Next after the Next after the Head, and that's C. So that's good.
mylist.Head
mylist.Head.Next
mylist.Head.Next.Next
mylist.Head.Next.Next.Next
mylist.Head.Next.Next.Prev
What about the end? mylist.Head.Next.Next.Next, that should be the end, and sure enough we've got the empty array. so far so good.
Now, let's go backwards. (Four dot operators in one expression, 5, 6. Just remember to think about what those dots are doing, going from left to right, one step at a time, and it's simple.)
mylist.Head.Next.Next.Prev.Prev
mylist.Head.Next.Next.Prev.Prev.Prev
we're back to that empty array at the beginning.
Let's test the remove function.
We'll remove a, and let's look at the Head of our list. Well, there's B, so A is gone.
mylist.remove(a)
mylist.Head
Let's go back one from there using Prev and we're at the beginning.
Now, let's go to the next element after the head, that should be C, and it is.
Removing the Head worked.
mylist.Head.Prev
mylist.Head.Next
How about removing c from the tail of these two element list?
Let's see. There should be just one node left. Let's see if that's right.
mylist.remove(c)
mylist
let's make sure that removing a and c has left B at the head of the list.
Check.
mylist.Head
mylist.Head.Next
mylist.Head.Prev
That there is no next element or previous element on this one element list.
Check.
Can we remove b? Sure can.
mylist.remove(b)
mylist
We have a list of length zero.
Now that we still have our letter objects, let's see.
Yeah, they're all still sitting there, and there next, prev, and owner handles are all set correctly to the empty array.
a
b
c
So we've created our very first link list, and then we removed every node from our very first link list.
We learned in the last lecture that we can add access methods to the methods section to intercept attempts by the user to access properties directly with the.operator, and we could write access methods that throw errors when a user tries to do it. But there's a much easier way. All we need to do is open up the DList class definition and set a so called attribute after the properties keyword and we do it like this, Access equals private.
% classdef DList < handle %version2
% properties (Access=private) %change
% Head
% Tail
% Length
% end
% methods
%
% function list = DList()
% list.Head = [];
% list.Tail = [];
% list.Length = 0;
% end
%
% function insert(list,node)
% if ~isempty(node.Owner)
% if node.Owner ~= list % New node is in another list,
% node.Owner.remove(node); % so we need to remove it.
% else
% return; % New node is already in this list,
% end % so do nothing.
% end
% if list.Length == 0 % If the list is empty,
% list.Head = node; % put new node at the head,
% else
% list.Tail.Next = node; % else, point tail node at it.
% end
% node.Next = []; % New node is at the end.
% node.Prev = list.Tail; % Previous node is old tail node.
% list.Tail = node; % Make Tail node point at new node.
% list.Length = list.Length + 1;
% node.Owner = list;
% end % insert
%
% function remove(list,node)
% if isempty(node) || node.Owner ~= list
% error('node is not in the list');
% end
% if ~isempty(node.Prev) % If a node precedes the current node,
% node.Prev.Next = node.Next; % make preceding node point to the node
% else % that follows the current node,
% list.Head = node.Next; % else make Head point to it.
% end
% if ~isempty(node.Next) % If a node follows the current node,
% node.Next.Prev = node.Prev; % make its prev point to the node that
% else % that precedes the current node
% list.Tail = node.Prev; % else make previous node be the tail.
% end
% list.Length = list.Length - 1;
% node.Next = [];
% node.Prev = [];
% node.Owner = [];
% end % remove
%
% function displayList(list)
% item = list.Head;
% while ~isempty(item)
% item.disp
% item = item.Next;
% end
% end
% end
% end
The parentheses are required that A must be capitalized and what looks like an assignment statement is actually a declaration that the Access attribute for the properties of this class is being set to private. With that, we have just put up a privacy fence that keeps prying eyes and grasping hands away from our private property. What happens if someone tries to peak at one of the DList properties?
this privacy fits really opaque as you'll see when you try to look at my list itself. This statement tells you that my list is an instance of a class that has no properties at all.
mylist
Well, that's just a lie, but it's a white lie. It's not meant to do harm to the user, but to keep the user from accidentally doing harm to DList. But can we still add items to our list?
Well, yes, we can.
mylist.insert(a)
mylist.insert(b)
mylist.insert(c)
mylist
when we ask what mylist looks like it says there are no properties, but it doesn't say that nothing has happened. Of course, it's not obvious that anything has happened. But we've also provided in this class a method that displays relevant information. Its like the PrintName method in the previous lecture and here it is.
It's called displayList and it uses a while loop to go through all the elements of the list, calling MATLAB built-in display function named disp, to display each one.
Let's test it,
mylist.displayList
By getting back to the private declaration, we've put a fence around DList, but we haven't done that for LetterNode and as a result, when disp displays a node, it can see its properties and prints them out.
a
But that's not actually good because it means that the user can see them too and could potentially set them to bogus values . We can protect the letter property from mischief by adding a set access method and a get access method. We showed you how to do that in our previous lecture. But we'll leave that for another time. So for now all we have to worry about are the prev, next, and owner properties that LetterNode inherits from LinkNode. Let's build a fence around those.
% classdef LinkedNode < handle
% properties (Access=private)
% Prev
% Next
% Owner
% end
% methods
% function node = LinkedNode()
% node.Prev = [];
% node.Next = [];
% node.Owner = [];
% end
% end
% end
%
now lets check
a
We're not seeing the Prev, Next, and Owner properties anymore, so yes, it worked. But look, of course it did, I know what I'm doing here. To prove it, let's run display list again.
%mylist.displayList
Not only have we stopped the user from accessing Prev, Next, and Owner, we've stopped, well, everybody, and that includes Dlist.
Now the while loop here in Dlist can't use the dot operator to access the next handle because the next pointer in all the rest of them are hidden. We need to hide the properties in link node from the user but not from Dlist.
How can we do that?
Well, things might get a little weird here. We replace the word private in this property declaration with question mark Dlist. This question mark Dlist syntax returns a so-called metaclass object. It's a metaclass because it's objects are classes. Question mark Dlist returns the class Dlist.
% classdef LinkedNode < handle
% properties (Access=?DList)
% Prev
% Next
% Owner
% end
% methods
% function node = LinkedNode()
% node.Prev = [];
% node.Next = [];
% node.Owner = [];
% end
% end
% end
The statement access equals question mark Dlist means that only Dlist has access to the properties. We've built a magic privacy fence that only Dlist can see through.
mylist.displayList
The length property of Dlist is private. On the one hand, that's good because nobody should be able to modify that number directly. On the other hand, users of our class would probably be interested to know how many elements that the list contains. Let's provide a method that returns the number of elements currently on the list.
Our initial thought might be to provide an access method like we've done before using the get.length syntax. That's not going to work though because length is private and MATLAB will not allow accessing it even if we provide an access function. Instead, we can simply write a regular method that returns the value directly. Like this.
% classdef DList < handle
% properties (Access = private)
% Head
% Tail
% Length
% end
% methods
%
% function lng = length(list) %change
% lng = list.Length;
% end
%
% function list = DList()
% list.Head = [];
% list.Tail = [];
% list.Length = 0;
% end
%
% function insert(list,node)
% if ~isempty(node.Owner)
% if node.Owner ~= list % New node is in another list,
% node.Owner.remove(node); % so we need to remove it.
% else
% return; % New node is already in this list,
% end % so do nothing.
% end
% if list.Length == 0 % If the list is empty,
% list.Head = node; % put new node at the head,
% else
% list.Tail.Next = node; % else, point tail node at it.
% end
% node.Next = []; % New node is at the end.
% node.Prev = list.Tail; % Previous node is old tail node.
% list.Tail = node; % Make Tail node point at new node.
% list.Length = list.Length + 1;
% node.Owner = list;
% end % insert
%
% function remove(list,node)
% if isempty(node) || node.Owner ~= list
% error('node is not in the list');
% end
% if ~isempty(node.Prev) % If a node precedes the current node,
% node.Prev.Next = node.Next; % make preceding node point to the node
% else % that follows the current node,
% list.Head = node.Next; % else make Head point to it.
% end
% if ~isempty(node.Next) % If a node follows the current node,
% node.Next.Prev = node.Prev; % make its prev point to the node that
% else % that precedes the current node
% list.Tail = node.Prev; % else make previous node be the tail.
% end
% list.Length = list.Length - 1;
% node.Next = [];
% node.Prev = [];
% node.Owner = [];
% end % remove
%
% function displayList(list)
% item = list.Head;
% while ~isempty(item)
% item.disp
% item = item.Next;
% end
% end
% end
% end
lets test it
mylist.length
length(mylist)
Well, there's still room for improvement for our doubly-linked list data structures, but we've covered more than enough for one lecture
Summary
1.Question What is the name of the built-in class that supports pointer semantics?
- object class
- link class
- access class
- handle class*
2.Question A user can modify a property of an instance of a value class inside a method of the class by
- defining a new property with a new value.
- deleting the object and recreating it again.
- ensuring the output argument of the method is set to be the object.*
- There is no way to do that in a value class.
3.Question Handle classes should be used carefully because
- a variable can be easily modified from the workspace.
- a user can write methods that change the properties of an object within the class definition that will be visible outside of the method's scope.*
- the methods inside the handle class are unknown.
- the properties inside the handle class are unknown.
4.Question How can a class prevent direct access to its properties?
- It cannot. All properties must be public so methods can use them.
- By making them private.*
- By setting methods to restricted access.
- By setting the properties to empty matrices.
5.Question Which of the following is incorrect?
- A class can consist of public, protected and private properties
- Private properties can be viewed by creating a display function or defining a get-access method.
- If a class declares its properties private, the properties of its subclasses become private as well.**
- A class can specify which other classes can access its properties.
More on OOP
we're going to keep working on the example that we've already introduced, our doubly linked list.
Let's create a new class called OrderedList that will insert items in increasing order.
There is one conceptual difficulty. A linked node class doesn't actually store any data, only its subclasses do that. Each subclass will decide what data to store and the data determines what sorting really means
For example, the method of determining the order of numbers would be different from the method of determining the order of strings. In other words, when we want to insert a node into the item list, we'll need to compare the new item with the existing items already in the list to be able to decide the proper location, and the comparison method will be different. How can we implement our OrderedList and our link node in a general way so that insertion will work for any subclass that we may add later? Well, object-oriented programming offers an elegant solution called operator overloading.
For sorting, we need to redefine the relational operators like less than, equal to and greater than. Confused? Well, that's because we haven't shown you an example. Let's cure that confusion with a quick addition to the link node class.
We're going to add another method section, and I'm going to give it an attribute that we haven't used before. The first thing to note is that a class definition can include multiple methods sections. We can also have multiple property sections, and each one starts with the methods or properties keyword and ends with the end keyword. The second thing to note is that you can assign an attribute to methods sections. In this case, the attribute is a new one called abstract. You can have different attributes like abstract and private for different sections. There's no limit on how many methods or property sections a class can have.
classdef LinkedNode < handle % LinkedNode_v4
properties (Access = ?DList)
Prev
Next
Owner
end
methods (Abstract)
gt(a,b)
end
methods
function node = LinkedNode()
node.Prev = [];
node.Next = [];
node.Owner = [];
end
end
end
gt give true if 2nd argument is less than 1st argument.
gt(6,3)
help gt
Overloading
if you define a method name gt in a class, the MATLAB will invoke that method instead of the built-in gt function whenever you compare two objects of that class with a greater than operator. That's called operator overloading. To be specific, defining a method to redefine a built-in function for specific classes of input arguments is called overloading. When that function is the implementation of an operator like the greater than sign, it's called operator overloading.
Well, remember that we don't want anybody to create an instance of the link node class. Instead, we want to restrict them to creating instances only of subclasses of link node. Well, we can enforce that restriction by marking the link node class as being abstract.
We can do that in three ways.
- First, by including the abstract attribute in the heading of the class definition.
- Second, by making any of its methods sections abstract.
- Third, by making any of its properties sections abstract.
Once a class is marked abstract, you can't create any instances of it. It's not allowed. It causes red things to happen.
Let's try it anyway.
% A=LinkedNode
Told you.
Furthermore, if you define a subclass of a class with any abstract methods or properties, then that subclass will also be an abstract class, with no instances allowed unless it includes a concrete implementation of every abstract method and every abstract property in its superclass, where concrete means non-abstract.
But we want to ensure that every subclass of linked node does have a gt inside it so that it's possible to compare nodes of those sub classes to maintain an ordered list. Guess what? We have ensure just that right here by including the signature of gt in an abstract methods section. Now, if we fail to include an implementation of gt in a subclass and then try to define an object of that subclass, MATLAB will get mad and MATLAB will get red.
For the sake of simplicity, let's make it a subclass of linked node that contains just a single number and call it sorted number.
classdef SortedNumber < LinkedNode % SortedNumber_v1
properties
Value
end
methods
function node = SortedNumber(n)
if ~isscalar(n) || ~isnumeric(n)
error('Expected numeric scalar Value');
end
node.Value = n;
end
function res = gt(node1,node2)
res = node1.Value > node2.Value;
end
end
end
Our new class adds a new property called value to the properties that are already inherent from linked node, which are next, prev, and owner. This properties where we're going to store the data for sorted number nodes.
It also adds two new methods, a constructor and a method with the same name gt as the abstract method that we defined in the superclass. This method provides the implementation of the abstract method in the superclass that we were just talking about so we can create sorted number objects.
But first, we're going to spend some time on this constructor, which takes just one input argument. We restrict that argument to be a scalar and to be numeric, and then assign it to the value property. We restrict it to be scalar because we want to keep the values in order and it's not so clear how to sort multi-element arrays. We restrict the value to be numeric because, well, look at the name of the class. To impose these two restrictions, we've used an if statement and the isscalar and isnumeric functions. We've done this sort of thing whenever we've needed to restrict a function's inputs. But recently, MATLAB has added a better way to impose restrictions on input arguments.
Let's use it.
classdef SortedNumber < LinkedNode % SortedNumber_v2
properties
Value
end
methods
function node = SortedNumber(n)
arguments
n (1,1) {mustBeNumeric} = 0
end
node.Value = n;
end
function res = gt(node1,node2)
res = node1.Value > node2.Value;
end
end
end
This is a brandy new feature of MATLAB introduced last year in 2019 B, with a brandy new keyword, arguments. Adding a new keyword to the language is a big deal because even with this new one, there's still only 25 keywords living in the entire vast kingdom of MATLAB. By keyword, I mean a word defined by the language to have a special meaning, better known as a blue word, because MATLAB paints its keywords blue.
now in the middle of object-oriented programming is a good time to introduce argument validation instead of in our lesson on functions. That's right, it works for normal functions too. Let's look more closely at this arguments block.
- First off, arguments blocks must come before anything else in the body of the function.
- Second, an arguments block must include one line for every formal argument in the function signature.
- Third, each line must start with the name of the formal argument.
- Fourth, the arguments lines must be in the same order as the arguments list.
On a given line after the name of the formal argument, you can include optional so-called qualifiers, each of which restricts in some way the value of that argument, which of course is not known until the function is called with actual arguments MATLAB highlights the qualifiers in this brown shade here.
This particular function has only one input argument, so there's only one line in the arguments block. Our first one gives the required size in this case one by one in parentheses. You can use the colon to allow any number for a given dimension. Our second qualifier is a list in braces of so-called validation functions. In this case, we include only one validation function and it's pretty clear what its restriction is, must be numeric.
Finally, you can add a default value at the end of the line, which is used if the actual argument is omitted. The syntax is simple, an equal sign followed by the desired value. We've given 0 as a default and you can see that its color is black instead of brown.
There are a few more wrinkles that we haven't mentioned. For example, in the size restriction qualifier, if the input has smaller dimensions than the specified shape but can be expanded to the specified shape by duplicating elements, it will be expanded instead of throwing an error. For example, if you specify a five element row vector, but the input is the number 3, it will be expanded to a row vector containing five threes. Also, argument validation can't be used in abstract methods or in nested functions. You can look up arguments in the documentation for more details,
Gt takes two nodes as arguments, which will both be objects of the class sorted number, and compares their value properties to determine whether the first one is greater than the second one and puts the result which is true or false into the output argument Res, R-E-S, which is an abbreviation for result. By the way, we can now see why we restricted the value property to be scaler in the constructor. It's so that when we compare the two value properties here with the greater than operator, we're guaranteed to be comparing scalers. Let's test it.
SortedNumber(3) > SortedNumber(4)
SortedNumber(4) > SortedNumber(3)
SortedNumber(3) > SortedNumber(4)
and let's see what happens if we use an operator that we haven't overloaded. Let's try the less than operator.
SortedNumber(3) < SortedNumber(4)
SortedNumber(4) < SortedNumber(3)
Well, it didn't throw an error, so that's good I guess. But what about the answers? Instead of checking them, let's just run it a few more times.Well, the answers are changing even though the operands aren't changing.
It certainly does not work correctly. So what's going on? Well, since we haven't implemented our own less than operator for the SortedNumber class, MATLAB has no idea that it's supposed to compare the value properties of the operands.
SortedNumber(5) < SortedNumber(4)
SortedNumber(4) < SortedNumber(5)
It's not important to us to know how it works, so we won't cover it here. You can read the MATLAB documentation if you like, but the important thing to remember is that while it won't throw an error if you want to use any of the relational operators on your class, you need to overload them, so
We'll first define abstract methods for all six of them in LinkedNode, greater than, greater than or equal, less than, less than or equal, equal, and not equal.
classdef LinkedNode < handle % LinkedNode_v5
properties (Access = ?DList)
Prev
Next
Owner
end
methods (Abstract)
gt(a,b)
ge(a,b)
lt(a,b)
le(a,b)
eq(a,b)
ne(a,b)
end
methods
function node = LinkedNode()
node.Prev = [];
node.Next = [];
node.Owner = [];
end
end
end
Now, let's add methods to implement them in the SortedNumber class itself.
classdef SortedNumber < LinkedNode % SortedNumber_v3
properties
Value
end
methods
function node = SortedNumber(n)
arguments
n (1,1) {mustBeNumeric} = 0
end
node.Value = n;
end
function res = gt(node1,node2) % >
res = node1.Value > node2.Value;
end
function res = ge(node1,node2) % >=
res = node1.Value >= node2.Value;
end
function res = lt(node1,node2) % <
res = node1.Value < node2.Value;
end
function res = le(node1,node2) % <=
res = node1.Value <= node2.Value;
end
function res = eq(node1,node2) % ==
res = node1.Value == node2.Value;
end
function res = ne(node1,node2) % ~=
res = node1.Value ~= node2.Value;
end
end
end
There they are all six of them. Let's test them with this script which compares the results of the overloaded operators on SortedNumber operands with a built-in operators on corresponding numeric operands. If the overloaded operators are working, we should get a result of true for every comparison.
(SortedNumber(3) <= SortedNumber(4)) == (3 <= 4)
(SortedNumber(3) > SortedNumber(4)) == (3 > 4)
(SortedNumber(3) == SortedNumber(4)) == (3 == 4)
(SortedNumber(3) == SortedNumber(3)) == (3 == 3)
(SortedNumber(3) ~= SortedNumber(4)) == (3 ~= 4)
(SortedNumber(3) <= SortedNumber(3)) == (3 <= 3)
(SortedNumber(3) < SortedNumber(3)) == (3 < 3)
Every result is true, so it passes every test. We've got all the overloaded operators working for SortedNumber. We built a class we need for our nodes. Now let's link them into a sorted list. We need to define a class for a sorted list.
Let's call it OrderedList instead of SortedList,
classdef DList < handle % DList_v3
properties (Access = private)
Head
Tail
Length
end
methods
function lng = length(list)
lng = list.Length;
end
function list = DList()
list.Head = [];
list.Tail = [];
list.Length = 0;
end
function insert(list,node)
if ~isempty(node.Owner)
if node.Owner ~= list % New node is in another list,
node.Owner.remove(node); % so we need to remove it.
else
return; % New node is already in this list,
end % so do nothing.
end
if list.Length == 0 % If the list is empty,
list.Head = node; % put new node at the head,
else
list.Tail.Next = node; % else, point tail node at it.
end
node.Next = []; % New node is at the end.
node.Prev = list.Tail; % Previous node is old tail node.
list.Tail = node; % Make Tail node point at new node.
list.Length = list.Length + 1;
node.Owner = list;
end % insert
function remove(list,node)
if isempty(node) || node.Owner ~= list
error('node is not in the list');
end
if ~isempty(node.Prev) % If a node precedes the current node,
node.Prev.Next = node.Next; % make preceding node point to the node
else % that follows the current node,
list.Head = node.Next; % else make Head point to it.
end
if ~isempty(node.Next) % If a node follows the current node,
node.Next.Prev = node.Prev; % make its prev point to the node that
else % that precedes the current node
list.Tail = node.Prev; % else make previous node be the tail.
end
list.Length = list.Length - 1;
node.Next = [];
node.Prev = [];
node.Owner = [];
end % remove
function displayList(list)
item = list.Head;
while ~isempty(item)
item.disp
item = item.Next;
end
end
end
end
But there's still some work to be done because while almost everything in DList will work for an OrderedList, the insert function will not work. That function always inserts the node we give it at the end of the list. But we want to insert the node at just the right place so that the list is kept in increasing order. Here's what the class definition for OrderedList looks like.
classdef OrderedList < DList % OrderedList_v1
methods
function insert(list,node)
if ~isempty(node.Owner)
if node.Owner ~= list
node.Owner.remove(node);
else
return;
end
end
node.Owner = list;
list.Length = list.Length + 1;
if isempty(list.Head)
list.Head = node;
list.Tail = node;
node.Prev = [];
node.Next = [];
else
cur = list.Head;
prev = [];
while ~isempty(cur) && node > cur
prev = cur;
cur = cur.Next;
end
if isempty(prev)
node.Next = list.Head;
node.Prev = [];
list.Head.Prev = node;
list.Head = node;
else
prev.Next = node;
node.Prev = prev;
node.Next = cur;
if isempty(cur)
list.Tail = node;
else
cur.Prev = node;
end
end
end
end
end
end
Like any subclass, it inherits the properties and methods of its superclass. But we've overloaded the insert method with a new version that keeps the node sorted.
Let's look at the Access attribute for the properties in DList. We've set it to private, which means that only DList objects can see them. That won't work for us because our OrderedList objects need to use these properties too, and private properties are not accessible to subclasses.
just change the access to public, but we don't need to do anything that drastic. Fortunately, there's a middle ground and it's called protected. Let's change the Access attribute from private to protected. This change extends the access to all subclasses of DList. So now OrderedList can access the properties of LinkedNode, and DList just by virtue of the fact that it's a subclass of DList.
classdef DList < handle % DList_v4
properties (Access = protected)
Head
Tail
Length
end
methods
function lng = length(list)
lng = list.Length;
end
function list = DList()
list.Head = [];
list.Tail = [];
list.Length = 0;
end
function insert(list,node)
if ~isempty(node.Owner)
if node.Owner ~= list % New node is in another list,
node.Owner.remove(node); % so we need to remove it.
else
return; % New node is already in this list,
end % so do nothing.
end
if list.Length == 0 % If the list is empty,
list.Head = node; % put new node at the head,
else
list.Tail.Next = node; % else, point tail node at it.
end
node.Next = []; % New node is at the end.
node.Prev = list.Tail; % Previous node is old tail node.
list.Tail = node; % Make Tail node point at new node.
list.Length = list.Length + 1;
node.Owner = list;
end % insert
function remove(list,node)
if isempty(node) || node.Owner ~= list
error('node is not in the list');
end
if ~isempty(node.Prev) % If a node precedes the current node,
node.Prev.Next = node.Next; % make preceding node point to the node
else % that follows the current node,
list.Head = node.Next; % else make Head point to it.
end
if ~isempty(node.Next) % If a node follows the current node,
node.Next.Prev = node.Prev; % make its prev point to the node that
else % that precedes the current node
list.Tail = node.Prev; % else make previous node be the tail.
end
list.Length = list.Length - 1;
node.Next = [];
node.Prev = [];
node.Owner = [];
end % remove
function displayList(list)
item = list.Head;
while ~isempty(item)
item.disp
item = item.Next;
end
end
end
end
lets see if this works.
First, we define three node objects: x, y, and z.
x = SortedNumber(1)
y = SortedNumber(11)
z = SortedNumber(111)
Next, we create a list and insert
mylist = OrderedList
mylist.insert(y)
mylist.insert(x) % out of order!
mylist.insert(z)
these nodes into that list, but not in order.
Now we look at the list to see whether they were inserted in proper order.
mylist.displayList();
Yes, they were. Let's insert some nodes directly and take another look.
Let's insert some nodes directly and take another look.
mylist.insert(SortedNumber(2))
mylist.insert(SortedNumber(-22))
mylist.insert(SortedNumber(222))
mylist.displayList();
working correctly
change the value
x.Value = 1000
check
mylist.displayList
we broke it because x is still right here where it was on the list when we inserted it before instead of being shifted to its rightful place, which is now the tail. Let's add code that will move it to the right place if its value is changed. It might seem that we would need to add the code to OrderedList since the list is going to have to be changed, but the trigger that causes the change has to be in SortedNumber, because we're directly accessing SortedNumber to change its Value property.
classdef SortedNumber < LinkedNode % SortedNumber_v4
properties
Value
end
methods
function node = SortedNumber(n)
arguments
n (1,1) {mustBeNumeric} = 0
end
node.Value = n;
end
function set.Value(node,newValue)
arguments
node
newValue (1,1) {mustBeNumeric}
end
if isempty(node.Owner)
node.Value = newValue;
else
list = node.Owner;
list.remove(node);
node.Value = newValue;
list.insert(node)
end
end
function res = gt(node1,node2) % >
res = node1.Value > node2.Value;
end
function res = ge(node1,node2) % >=
res = node1.Value >= node2.Value;
end
function res = lt(node1,node2) % <
res = node1.Value < node2.Value;
end
function res = le(node1,node2) % <=
res = node1.Value <= node2.Value;
end
function res = eq(node1,node2) % ==
res = node1.Value == node2.Value;
end
function res = ne(node1,node2) % ~=
res = node1.Value ~= node2.Value;
end
end
end
<this MATLAB 2018 so further will not suppot>
Practice Quiz
1.Question Operator overloading means
- redefining the behavior of built-in MATLAB operators for instances of the given class.*
- providing multiple implementations of certain operators for a given class.
- testing operators with large operands to make sure they can handle them.
- None of the above.
2.Question A class having at least one abstract method means that
- it cannot be instantiated.
- its subclasses will need to implement all of its abstract methods otherwise they would remain abstract classes themselves.
- it is an abstract class.
- All of the above***
3.Question Which of the following statements is false?
- Argument validation works with ordinary functions, methods of classes and even nested functions.****
- Argument validation can be used to check the type and size of the actual input arguments used to call the function.
- Argument validation can save the programmer work and make the code more readable by not having to write extra code that checks the assumptions about the input arguments of functions.
- Argument validation can be used to provide default values to input arguments of functions, hence, such functions can be called with the given arguments omitted.
4.Question Which of the following is not a function implementing a relational operator?
- eq
- ne
- st*
- gt
5.Question Which of the following statements is true?
- A subclass inherits all properties and methods of its superclass.
- There is no way for a class to access the private properties of another class.
- When a class A is put on the access list of class B, all subclasses of class A have the same access to class B as class A itself.**
- None of the above